#[cfg(test)]
use proptest::prelude::*;
use std::{
    fmt::{self, Debug, Display, Formatter, Write},
    num::NonZeroU8,
};

#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Level {
    /// 指数
    ///
    /// 2048 没有 1，最小也是 2，因此指数一定非零。
    pub exp: NonZeroU8,
}

impl Level {
    /// 如果[`u64`]不够表示，则返回空。
    pub fn try_get(self) -> Option<u64> {
        1u64.checked_shl(self.exp.get().into())
    }

    pub fn get(self) -> u64 {
        1u64 << self.exp.get()
    }

    /// 如果溢出，则返回空。
    pub fn upgrade(self) -> Option<Self> {
        let exp = self.exp.checked_add(1)?;
        Some(Self { exp })
    }

    /// 简单的格式化
    ///
    /// 一般以整数显示，若比给定的宽度长，则换用指数。
    fn fmt_plain(self, width: Option<usize>, mut w: impl Write) -> fmt::Result {
        // 必须小于该数，宽度才够；如果为空，那么无限制
        let max_num = width
            .and_then(|width| width.try_into().ok()) // 失败就是太大
            .and_then(|width| 10u64.checked_pow(width)); // 溢出就是太大
        let num = self
            .try_get()
            .filter(|&exp| max_num.map_or(true, |m| exp < m));
        if let Some(num) = num {
            write!(w, "{num}")
        } else {
            write!(w, "2^{}", self.exp)
        }
    }
}

impl Debug for Level {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        write!(f, "2^{}", self.exp)
    }
}

/// 处理宽度，自动处理对齐、填充、符号等，忽略精度。
///
/// 尽量不超过宽度。一般以整数显示，若比给定的宽度长，则换用指数。
impl Display for Level {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        let mut s = String::with_capacity(4);
        self.fmt_plain(f.width(), &mut s).expect("格式化失败");
        f.pad_integral(true, "", &s)
    }
}

#[cfg(test)]
impl Arbitrary for Level {
    type Parameters = ();
    type Strategy = BoxedStrategy<Level>;
    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
        (1..50u8) // 这个数字实际上也不会很大
            .prop_map(|exp| Level {
                exp: exp.try_into().unwrap(),
            })
            .boxed()
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn fmt() {
        let new = |exp| Level {
            exp: NonZeroU8::new(exp).unwrap(),
        };
        assert_eq!(format!("{:+}", new(1)), "+2");
        assert_eq!(format!("{:10}", new(10)), "      1024");
        assert_eq!(format!("{}", new(50)), "1125899906842624");
        assert_eq!(format!("{}", new(128)), "2^128");
        assert_eq!(format!("{:^+5}", new(3)), " +8  ");
        assert_eq!(format!("{:4}", new(11)), "2048");
        assert_eq!(format!("{:+3}", new(11)), "+2^11");
        assert_eq!(format!("{:+4}", new(13)), "+8192");
        assert_eq!(format!("{:1}", new(50)), "2^50");
    }
}
